package lumis.portal.channel;

import lumis.portal.AccessDeniedException;
import lumis.portal.PortalContext;
import lumis.portal.PortalException;
import lumis.portal.PortalObjectAlreadyExistsException;
import lumis.portal.PortalObjectNotFoundException;
import lumis.portal.PortalRequestParameters;
import lumis.portal.UnexpectedException;
import lumis.portal.acl.PortalPermissions;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.cache.PortalCache;
import lumis.portal.channel.acl.ChannelPermissions;
import lumis.portal.dao.DaoFactory;
import lumis.portal.el.LumisExpressionEvaluatorImpl;
import lumis.portal.event.persistence.PostAddEvent;
import lumis.portal.event.persistence.PostDeleteEvent;
import lumis.portal.event.persistence.PostUpdateEvent;
import lumis.portal.event.persistence.PreDeleteEvent;
import lumis.portal.group.GroupConfig;
import lumis.portal.group.IGroupManager;
import lumis.portal.manager.ManagerException;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.page.IPageManager;
import lumis.portal.page.PageConfig;
import lumis.portal.page.cache.PageCacheConfig;
import lumis.portal.page.webresource.DefaultPageWebResourceDataProvider;
import lumis.portal.propertybag.Property;
import lumis.portal.propertybag.PropertyBag;
import lumis.portal.serialization.DeserializationConfig;
import lumis.portal.serialization.DeserializationContext;
import lumis.portal.serialization.ISerializationManager2;
import lumis.portal.serialization.SerializationContext;
import lumis.portal.serialization.DeserializationContext.ExistingStyleAction;
import lumis.portal.serialization.persistency.IDirectory;
import lumis.portal.serialization.persistency.fs.FSShallowDirectory;
import lumis.portal.serviceinstance.ServiceInstanceConfig;
import lumis.portal.serviceinstance.ServiceInstanceManager;
import lumis.portal.transaction.AbstractTransactionObserver;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.portal.url.IURLManager;
import lumis.portal.url.IWebResourceFactory;
import lumis.portal.url.WebResource;
import lumis.portal.user.IUserManager;
import lumis.portal.user.UserConfig;
import lumis.util.ITransaction;
import lumis.util.PortalUtil;
import lumis.util.TextUtil;
import lumis.util.XmlUtil;
import lumis.util.log.ILogger;
import lumis.util.log.LoggerFactory;
import lumis.util.security.acl.AccessControlList;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.persistence.EntityManager;
import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.VariableResolver;

import org.w3c.dom.Node;

/**
 *	Implementation for the channel manager.
 *
 *	@since 4.0.0
 *	@version $Revision: 13160 $ $Date: 2011-06-21 18:50:57 -0300 (Tue, 21 Jun 2011) $
 */
public class ChannelManager implements IChannelManagerSPI, IWebResourceFactory
{
	private static ILogger logger = LoggerFactory.getLogger(ChannelManager.class);

	/**
	 * Prefix of the transaction attribute name used to indicate that a
	 * channel's web resource was set. Appended to this prefix is the channel
	 * identifier.
	 * @see #setWebResourcesMissingData(String, boolean)
	 * @see #doSetWebResourcesMissingData(ChannelConfig, boolean, boolean)
	 * @since 5.0.0
	 */
	private static final String ATTRIBUTE_PREFIX_CHANNEL_WEB_RESOURCE_SET = "lumis.portal.channel.ChannelManager#webResourceSet-";

	/**
	 * Value used as key in {@link #subChannelsCache} for channel id value
	 * of null.
	 * @since 4.2.0
	 */
	private static final String NULL_CHANNEL_ID_CACHE_KEY = "NULL_CHANNEL_ID_VALUE";

	private PortalCache<ChannelConfig> channelConfigCache = new PortalCache<ChannelConfig>("lumis.portal.channel.ChannelConfig");

	/**
	 * Cache used by {@link #getIdsByParentChannelId(SessionConfig, String, ITransaction)}.
	 * @since 4.2.0
	 */
	private PortalCache<List<String>> subChannelsCache = new PortalCache<List<String>>("lumis.portal.channel.ChannelConfig.subChannels");

	/**
	 * Enum of audit types used by channel.
	 *
	 * @version $Revision: 13160 $ $Date: 2011-06-21 18:50:57 -0300 (Tue, 21 Jun 2011) $
	 * @since 6.0.0
	 */
	private static enum AuditType
	{
		ADD("lumis.portal.channel.ChannelConfig#add", "Added channel"),
		UPDATE("lumis.portal.channel.ChannelConfig#update", "Updated channel"),
		DELETE("lumis.portal.channel.ChannelConfig#delete", "Deleted channel");

		String key;
		String title;

		AuditType(String key, String title)
		{
			this.key = key;
			this.title = title;
		}
	}

	private void validate(SessionConfig sessionConfig, ChannelConfig channelConfig, ITransaction transaction) throws PortalException
	{
		if (channelConfig.isInheritPageTemplate() && channelConfig.getPageTemplateId() != null)
				throw new ManagerException("STR_INHERITS_AND_DEFINES_LOCAL_PAGE_TEMPLATE");

		String channelTemplateId = channelConfig.getParentTemplateId();
		if (channelTemplateId != null)
		{
			if(channelConfig.isTemplate())
				throw new ManagerException("STR_TEMPLATE_CHANNEL_WITH_PARENT_TEMPLATE_CHANNEL");

			ChannelConfig channelTemplateConfig;
			try
			{
				channelTemplateConfig = get(sessionConfig, channelTemplateId, transaction);
			}
			catch (PortalObjectNotFoundException e)
			{
				channelTemplateConfig = null;
			}
			if (channelTemplateConfig == null || !channelTemplateConfig.isTemplate())
				throw new ManagerException("STR_CHANNEL_TEMPLATE_NOT_FOUND");

			String parentChannelId = channelConfig.getParentId();
			while (parentChannelId != null)
			{
				if (parentChannelId.equals(channelTemplateId))
					throw new ManagerException("STR_CHANNEL_CANNOT_USE_AN_ANCESTOR_TEMPLATE");
				parentChannelId = get(sessionConfig, parentChannelId, transaction).getParentId();
			}
		}
	}
	public void addOrUpdate(SessionConfig sessionConfig, ChannelConfig channelConfig, ITransaction transaction) throws ManagerException, PortalException
	{
		try
		{
			ChannelConfig channelConfigTemp = null;

			try
			{
				channelConfigTemp = get(sessionConfig, channelConfig.getId(), transaction);
			}
			catch (Exception e)
			{
				channelConfigTemp = null;
			}

			if(channelConfigTemp != null)
				update(sessionConfig, channelConfig, transaction);
			else
				add(sessionConfig, channelConfig, transaction);
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	private String generateLocalGroupPrefix(SessionConfig sessionConfig, ChannelConfig channelConfig, ITransaction transaction) throws PortalException
	{
		// search for a channel template ancestor
		ChannelConfig channelTemplate = channelConfig;
		while (channelTemplate != null && !channelTemplate.isTemplate())
		{
			String parentId = channelTemplate.getParentId();
			if (parentId != null)
				channelTemplate = get(sessionConfig, parentId, transaction);
			else
				channelTemplate = null;
		}

		if (channelTemplate == null)
		{
			// no conversion needed
			return channelConfig.getTemplateLocalGroupPrefix();
		}
		else
		{
			// apply EL evaluating 'rootChannel' to the channelTemplate found
			LumisExpressionEvaluatorImpl expressionEvaluator = new LumisExpressionEvaluatorImpl();
			VariableResolver variableResolver = new TemplateLocalPrefixVariableResolver(channelTemplate);
			try
			{
				return (String)expressionEvaluator.evaluate(channelConfig.getTemplateLocalGroupPrefix(), String.class, variableResolver, null);
			}
			catch (ELException e)
			{
				// if not possible to evaluate, use the original value
				return channelConfig.getTemplateLocalGroupPrefix();
			}
		}
	}

	private boolean exists(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		boolean exists = false;
		try
		{
			ChannelConfig channelConfigTemp = get(sessionConfig, channelId, transaction);
			if(channelConfigTemp != null)
				exists = true;
		}
		catch(PortalObjectNotFoundException e)
		{
			// catch here means that the object does not exist.
		}
		return exists;
	}

	public String add(SessionConfig sessionConfig, ChannelConfig channelConfig, ITransaction transaction) throws ManagerException, PortalException
	{
		try
		{
			validate(sessionConfig, channelConfig, transaction);

			if (!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, channelConfig.getParentId(), ChannelPermissions.MANAGE_CHANNEL, transaction))
				throw new AccessDeniedException();

			if (channelConfig.hasLocalGroups())
			{
				String templateLocalGroupPrefix = channelConfig.getTemplateLocalGroupPrefix();
				if (templateLocalGroupPrefix == null || templateLocalGroupPrefix.trim().length() == 0)
					throw new ManagerException("STR_LOCAL_GROUPS_PREFIX_REQUIRED");
				if (channelConfig.getLocalGroupPrefix() == null || channelConfig.getLocalGroupPrefix().trim().length() == 0)
				{
					String localGroupPrefix = generateLocalGroupPrefix(sessionConfig, channelConfig, transaction);
					channelConfig.setLocalGroupPrefix(localGroupPrefix);
				}
			}

			if (channelConfig.isAutomaticFriendlyId())
			{
				channelConfig.setAutoGeneratedFriendlyId();
			}


			if (transaction.getAttribute(ISerializationManager2.ATTRIBUTE_DESERIALIZING) == null)
			{
				validateFriendlyId(sessionConfig, channelConfig, transaction);
			}

			if (channelConfig.getId() == null)
			{
				channelConfig.setId(PortalUtil.generateNewGuid());
			}
			else
			{
				if (exists(sessionConfig, channelConfig.getId(), transaction))
					throw new PortalObjectAlreadyExistsException("STR_CHANNEL_ALREADY_EXISTS;"+channelConfig.getId());
			}

			// perform inserts in the database
			if(channelConfig.getAccessControlListId() == null)
				ManagerFactory.getChannelAclManager().add(sessionConfig, channelConfig, transaction);

			// insert the propertyBag
			if(channelConfig.getPropertyBagId() == null)
			{
				PropertyBag propertyBag = new PropertyBag();
				if(channelConfig.getParentId() != null)
				{
					ChannelConfig parentChannel = get(sessionConfig, channelConfig.getParentId(), transaction);
					PropertyBag parentChannelPropertyBag = ManagerFactory.getPropertyBagManager().get(parentChannel.getPropertyBagId());
					propertyBag.setParentPropertyBag(parentChannelPropertyBag);
				}

				ManagerFactory.getPropertyBagManager().add(propertyBag);
				ManagerFactory.getEntityManager().flush();

				channelConfig.setPropertyBagId(propertyBag.getId());
			}

			DaoFactory.getChannelDao().add(channelConfig, transaction);

			// apply changes to the property bag
			channelConfig.applyPropertyBagChanges();

			// evict subChannels cache of parent channel
			String cacheKey = channelConfig.getParentId();
			if (cacheKey == null)
				cacheKey = NULL_CHANNEL_ID_CACHE_KEY;
			subChannelsCache.remove(cacheKey, transaction);

			// send add event
			PostAddEvent event = new PostAddEvent(sessionConfig,
					ChannelConfig.class, channelConfig, transaction);
			ManagerFactory.getPortalEventManager().notifyObservers(event);


			// audit
			audit(sessionConfig, channelConfig, AuditType.ADD, transaction);

			return channelConfig.getId();
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	public void validateFriendlyId(SessionConfig sessionConfig, ChannelConfig channelConfig,
			ITransaction transaction) throws PortalException
	{
		if (channelConfig.getFriendlyId() == null)
			throw new ManagerException("STR_FRIENDLY_ID_CANNOT_BE_NULL");

		if (!channelConfig.mayUseFriendlyId(channelConfig.getFriendlyId()))
			throw new ManagerException("STR_FRIENDLY_ID_CANNOT_BE_USED;"
					+ channelConfig.getFriendlyId());
	}

	/**
	 * Call the audit API registering the channel operations.
	 * @param sessionConfig		instance of SessionConfig.
	 * @param channelConfig		instance of ChannelConfig.
	 * @param auditType			the audit opertion type.
	 * @param transaction		the current transaction.
	 * @throws PortalException if an error occur registering the audit opertion.
	 * @since 6.0.0
	 */
	private void audit(SessionConfig sessionConfig,
						ChannelConfig channelConfig,
						AuditType auditType,
						ITransaction transaction)
					throws PortalException
	{
		ManagerFactoryInternal.getAuditManager().audit(auditType.title + ": " + channelConfig.getName(),
				auditType.title + ":\r\n"
				+ "  id: " + channelConfig.getId() + "\r\n"
				+ "  name: " + channelConfig.getName() + "\r\n"
				+ "  description: " + channelConfig.getDescription(),
				auditType.key);
	}

	public void update(SessionConfig sessionConfig, final ChannelConfig channelConfig, ITransaction transaction) throws PortalException
	{
		try
		{
			validate(sessionConfig, channelConfig, transaction);

			if (!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, channelConfig.getId(), ChannelPermissions.MANAGE_CHANNEL, transaction))
				throw new AccessDeniedException();

			if (mustUpdateFriendlyId(sessionConfig, channelConfig, transaction))
			{
				channelConfig.setAutoGeneratedFriendlyId();
			}

			if (transaction.getAttribute(ISerializationManager2.ATTRIBUTE_DESERIALIZING) == null)
			{
				validateFriendlyId(sessionConfig, channelConfig, transaction);
			}

			ChannelConfig oldConfig = get(sessionConfig, channelConfig.getId(), transaction);

			// if switching from simple channel to channel template, clear all links
			if(oldConfig.isTemplate() == false && channelConfig.isTemplate() == true)
				ManagerFactory.getChannelTemplateManager().deleteChannelLinks(sessionConfig, channelConfig.getId(), transaction);

			boolean moveRequested = false;

			if( (oldConfig.getParentId() == null || channelConfig.getParentId() == null) && (oldConfig.getParentId() != channelConfig.getParentId()))
				moveRequested = true;
			else if (oldConfig.getParentId() != null && !oldConfig.getParentId().equals(channelConfig.getParentId()))
				moveRequested = true;

			if(!oldConfig.getAccessControlListId().equals(channelConfig.getAccessControlListId()))
				throw new ManagerException("STR_CHANNEL_ACLID_MAY_NOT_BE_CHANGED_ON_UPDATE");

			if (channelConfig.getPropertyBagId() == null)
				channelConfig.setPropertyBagId(oldConfig.getPropertyBagId());
			PropertyBag propertyBag = ManagerFactory.getPropertyBagManager().get(channelConfig.getPropertyBagId());

			if(moveRequested)
			{
				// channel is being moved, need to do some verifications
				if(channelConfig.getId().equals(ChannelConfig.PORTAL_CHANNEL_ID) || channelConfig.getId().equals(ChannelConfig.PORTAL_CHANNEL_ID))
					throw new ManagerException("STR_FRAMEWORK_CHANNELS_CANNOT_BE_MOVED");
				if(channelConfig.getParentId() == null)
					throw new ManagerException("STR_CHANNEL_MUST_HAVE_A_PARENT");
				if(channelConfig.getParentId().equals(channelConfig.getId()))
					throw new ManagerException("STR_CHANNEL_CANNOT_BE_MOVED_INTO_ITSELF");

				// need to check if the channel is being moved into one of his subchannels
				String parentId = channelConfig.getParentId();
				ChannelConfig tempConfig;
				while(true)
				{
					tempConfig = get(sessionConfig, parentId, transaction);
					parentId = tempConfig.getParentId();
					if(parentId == null || parentId.equals(ChannelConfig.PORTAL_CHANNEL_ID))
					{
						break;
					}
					else if(parentId.equals(channelConfig.getId()))
					{
						// parent being moved into child
						throw new ManagerException("STR_CHANNEL_CANNOT_BE_MOVED_INTO_SUBCHANNEL");
					}
				}

				// get channel's parent channel and acl
				ChannelConfig parentChannelConfig = ManagerFactory.getChannelManager().get(sessionConfig, channelConfig.getParentId(), transaction);
				AccessControlList channelAcl = ManagerFactory.getChannelAclManager().get(sessionConfig, channelConfig.getId(), transaction);

				// change the acl's parent to parent channel's acl
				channelAcl.setParentId(parentChannelConfig.getAccessControlListId());
				ManagerFactory.getChannelAclManager().update(sessionConfig, channelConfig.getId(), channelAcl, transaction);

				// change the property bag's parent property bag
				propertyBag.setParentPropertyBag(ManagerFactory.getPropertyBagManager().get(parentChannelConfig.getPropertyBagId()));
			}

			if (channelConfig.hasLocalGroups())
			{
				String templateLocalGroupPrefix = channelConfig.getTemplateLocalGroupPrefix();
				if (templateLocalGroupPrefix == null || templateLocalGroupPrefix.trim().length() == 0)
					throw new ManagerException("STR_LOCAL_GROUPS_PREFIX_REQUIRED");
				if (channelConfig.getLocalGroupPrefix() == null || channelConfig.getLocalGroupPrefix().trim().length() == 0)
				{
					String localGroupPrefix = generateLocalGroupPrefix(sessionConfig, channelConfig, transaction);
					channelConfig.setLocalGroupPrefix(localGroupPrefix);
				}

				// MUST Update the channel before updating the users and groups
				DaoFactory.getChannelDao().update(channelConfig, transaction);
				channelConfigCache.remove(channelConfig.getId(), transaction);
				subChannelsCache.clear(transaction);

				// update local users and groups prefix
				if (oldConfig.getLocalGroupPrefix() == null || !oldConfig.getLocalGroupPrefix().equals(channelConfig.getLocalGroupPrefix()))
				{
					Pattern p = Pattern.compile("^" + oldConfig.getLocalGroupPrefix() + "\\.");
					Matcher m;

					IGroupManager groupManager = ManagerFactory.getGroupManager();
					Collection<GroupConfig> groupConfigs = groupManager.getByChannelId(sessionConfig, channelConfig.getId(), transaction);
					for (GroupConfig groupConfig: groupConfigs)
					{
						m = p.matcher(groupConfig.getAlias());
						groupConfig.setAlias(m.replaceFirst(channelConfig.getLocalGroupPrefix() + "."));
						groupManager.update(sessionConfig, groupConfig, transaction);
					}

					IUserManager userManager = ManagerFactory.getUserManager();
					Collection<UserConfig> userConfigs = userManager.getByChannelId(sessionConfig, channelConfig.getId(), transaction);
					for (UserConfig userConfig: userConfigs)
					{
						m = p.matcher(userConfig.getLogin());
						userConfig.setLogin(m.replaceFirst(channelConfig.getLocalGroupPrefix() + "."));
						userManager.update(sessionConfig, userConfig, transaction);
					}
				}
			}
			else
			{
				if (oldConfig.hasLocalGroups())
				{
					removeLocalUsersAndGroups(sessionConfig, channelConfig.getId(), transaction);
				}

				// TODO remove channel dependencies (pages, templates, service
				// instances, etc)
				DaoFactory.getChannelDao().update(channelConfig, transaction);
				channelConfigCache.remove(channelConfig.getId(), transaction);
				subChannelsCache.clear(transaction);
			}

			// apply changes to the property bag
			channelConfig.applyPropertyBagChanges();

			// update channel's web resources data
			// (schedule to beforeCommit to allow property bag changes.
			// Not best way to get these changes, but this is a fast immediate implementation)
			transaction.addObserver(new AbstractTransactionObserver()
			{
				@Override
				public void afterCommit() throws PortalException
				{
					SessionConfig sessionConfig = ManagerFactory.getAuthenticationManager().impersonate(UserConfig.USER_SYSTEM_ID);
					try
					{
						ITransaction transaction = PortalTransactionFactory.createTransaction();
						try
						{
							transaction.begin();
							setWebResourcesMissingData(channelConfig.getId(), true);
							transaction.commit();
						}
						finally
						{
							transaction.dispose();
						}
					}
					finally
					{
						ManagerFactory.getAuthenticationManager().endImpersonation(sessionConfig);
					}
				}
			});

			// send update event
			PostUpdateEvent postUpdateEvent = new PostUpdateEvent(sessionConfig,
					ChannelConfig.class, oldConfig, channelConfig, transaction);
			ManagerFactory.getPortalEventManager().notifyObservers(postUpdateEvent);

			// send render data changed event
			ChannelRenderDataChangedEvent changedEvent = new ChannelRenderDataChangedEvent(sessionConfig, Collections.singleton(channelConfig), transaction);
			ManagerFactory.getPortalEventManager().notifyObservers(changedEvent);

			// audit
			audit(sessionConfig, channelConfig, AuditType.UPDATE, transaction);
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	public String copy(SessionConfig sessionConfig, String sourceChannelId, String destinationParentChannelId, ITransaction transaction) throws ManagerException, PortalException
	{
		if (!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, destinationParentChannelId,
				ChannelPermissions.MANAGE_CHANNEL, transaction))
			throw new AccessDeniedException();
		IDirectory directory = null;
		try
		{
			if(sourceChannelId.equals(ChannelConfig.PORTAL_CHANNEL_ID))
			{
				throw new ManagerException("STR_CHANNEL_CANNOT_COPY_PORTAL_CHANNEL");
			}

			ISerializationManager2 serializationManager = ManagerFactoryInternal.getSerializationManager2();
			String processId = PortalUtil.generateNewGuid();
			directory = new FSShallowDirectory(PortalContext.getDataPath("sessions/" + sessionConfig.getUserSessionId() + "/" + PortalUtil.generateNewGuid()));
			//directory = new RamDirectory();

			// serialize channel
			SerializationContext serializationContext = new SerializationContext(sessionConfig, transaction, sourceChannelId, directory, false, false);
			serializationManager.serializeChannel(serializationContext);

			// deserialize channel
			boolean createNewIds = true;
			boolean updateIfElementExists = true;
			boolean importAsChild = true;
			boolean deleteExistingElements = true;
			boolean simulation = false;
			ExistingStyleAction existingStyleAction = ExistingStyleAction.SKIP;
			DeserializationContext deserializationContext = new DeserializationContext(sessionConfig, transaction, destinationParentChannelId, directory, createNewIds, updateIfElementExists, importAsChild, deleteExistingElements, simulation, existingStyleAction, false, false);
			deserializationContext.setProcessId(processId);

			// import channel
			return serializationManager.deserializeChannel(deserializationContext);
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
		finally
		{
			if (directory != null)
			{
				try
				{
					directory.delete();
				}
				catch (IOException e)
				{
					logger.debug("Error deleting directory.", e);
				}
			}
		}

	}

	private void removeLocalUsersAndGroups(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		// remove local users, groups and permissions
		IGroupManager groupManager = ManagerFactory.getGroupManager();
		Collection<GroupConfig> groupConfigs = groupManager.getByChannelId(sessionConfig, channelId, transaction);
		for (GroupConfig groupConfig: groupConfigs)
		{
			groupManager.delete(sessionConfig, groupConfig.getId(), transaction);
		}

		IUserManager userManager = ManagerFactory.getUserManager();
		Collection<UserConfig> userConfigs = userManager.getByChannelId(sessionConfig, channelId, transaction);
		for (UserConfig userConfig: userConfigs)
		{
			userManager.delete(sessionConfig, userConfig.getId(), transaction);
		}

	}

	public ChannelConfig get(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		ChannelConfig channelConfig = channelConfigCache.get(channelId);
		if (channelConfig == null)
		{
			channelConfig = DaoFactory.getChannelDao().get(channelId, transaction);
			channelConfigCache.put(channelId, channelConfig);
		}
		channelConfig.loadPropertyBagValues();
		return channelConfig;
	}

	public void delete(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		if (!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, channelId, ChannelPermissions.MANAGE_CHANNEL, transaction))
			throw new AccessDeniedException();

		ChannelConfig channelConfig = ManagerFactory.getChannelManager().get(sessionConfig, channelId, transaction);

		disconnectChannelsFromTemplatesRecursively(sessionConfig, channelId, transaction);
		deleteRecursively(sessionConfig, channelId, transaction);

		// audit
		audit(sessionConfig, channelConfig, AuditType.DELETE, transaction);
	}

	protected void disconnectChannelsFromTemplatesRecursively(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		ChannelConfig channelConfig = get(sessionConfig, channelId, transaction);
		if(channelConfig.getParentTemplateId() != null)
		{
			ManagerFactory.getChannelTemplateManager().disconnectFromTemplate(sessionConfig, channelId, transaction);
		}

		IChannelDao channelDao = DaoFactory.getChannelDao();
		// disconnect subchannels
		List<String> subChannelIds = channelDao.getSubChannelIds(channelId, transaction);
		for(String subChannelId : subChannelIds)
		{
			disconnectChannelsFromTemplatesRecursively(sessionConfig, subChannelId, transaction);
		}
	}

	protected void deleteRecursively(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		if (!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, channelId, ChannelPermissions.MANAGE_CHANNEL, transaction))
			throw new AccessDeniedException();

		// read channel's data (used later in this method)
		ChannelConfig channelConfig = get(sessionConfig, channelId, transaction);

		IChannelDao channelDao = DaoFactory.getChannelDao();

		// avoid to delete Portal channel
		if (channelId.equals(ChannelConfig.PORTAL_CHANNEL_ID))
			throw new ManagerException("STR_PORTAL_CHANNEL_CANNOT_BE_DELETED");

		// Check if the channel being deleted is a template and if so, check if it's being used.
		if(channelConfig.isTemplate())
		{
			List<String> childChannels = getIdsByParentTemplateId(sessionConfig, channelConfig.getId(), transaction);
			if(childChannels.size() > 0)
				throw new PortalException("STR_CHANNEL_TEMPLATE_IS_BEING_USED;"+channelConfig.getName());
		}

		// send pre delete event
		PreDeleteEvent preDeleteEvent = new PreDeleteEvent(sessionConfig, ChannelConfig.class, channelConfig, transaction);
		ManagerFactory.getPortalEventManager().notifyObservers(preDeleteEvent);

		// get all channels in the tree
		Collection<ChannelConfig> treeChannels = new ArrayList<ChannelConfig>();
		getChannelsFromChannelTree(sessionConfig, channelId, treeChannels, transaction);

		// delete all pages in the channel hierarchy
		List<String>templatePageIds = new ArrayList<String>();
		for (ChannelConfig currentChannel: treeChannels)
		{
			List<String> pageIds = ManagerFactory.getPageManager().getIdsByParentChannelId(sessionConfig, currentChannel.getId(), transaction);
			for (String pageId : pageIds)
			{
				PageConfig pageConfig = ManagerFactory.getPageManager().get(sessionConfig, pageId, transaction);
				if(!pageConfig.isTemplate())
					ManagerFactory.getPageManager().delete(sessionConfig, pageId, transaction);
				else
					templatePageIds.add(pageId);
			}
		}

		// delete the page templates
		for (String templatePageId : templatePageIds)
		{
			// disconnect dependent pages
			Collection<String> dependantPages = ManagerFactory.getPageManager().getIdsByParentTemplateId(sessionConfig, templatePageId, transaction);
			for (String dependantPage : dependantPages)
				ManagerFactory.getPageManager().disconnectFromTemplate(sessionConfig, dependantPage, transaction);

			// delete the template
			ManagerFactory.getPageManager().delete(sessionConfig, templatePageId, transaction);
		}

		// subChannels cache must be cleared
		subChannelsCache.clear(transaction);

		// create a set with treeChannels identifiers, for use in the for below
		Set<String> treeChannelIds = new HashSet<String>(treeChannels.size());

		// put all channels before the channelTamplates
		Set<String> treeChannelTemplateIds = new HashSet<String>(treeChannels.size());
		for (ChannelConfig currentChannel: treeChannels)
		{
			// separate channels and templates
			if(!currentChannel.isTemplate())
				treeChannelIds.add(currentChannel.getId());
			else
				treeChannelTemplateIds.add(currentChannel.getId());
		}
		// join all channels and templates
		treeChannelIds.addAll(treeChannelTemplateIds);

		// verify if some template has external channels using then
		for(String channelTemplateId: treeChannelTemplateIds)
		{
			List<String> idsByParentTemplateId = getIdsByParentTemplateId(sessionConfig, channelTemplateId, transaction);
			for(String channelIdByParentTemplateId: idsByParentTemplateId)
			{
				// if some external channel uses some channel template to be delete throws the exception below.
				if(!treeChannelIds.contains(channelIdByParentTemplateId))
				{
					String path = ManagerFactory.getChannelManager().getPath(sessionConfig, channelTemplateId, transaction);
					throw new PortalException("STR_CHANNEL_TEMPLATE_IS_BEING_USED;"+path);
				}
			}
		}

		for (ChannelConfig currentChannel: treeChannels)
		{
			String currentChannelId = currentChannel.getId();

			// remove service instances
			Collection<String> serviceInstanceIds = ManagerFactory.getServiceInstanceManager().getIdsByChannelId(sessionConfig, currentChannelId, transaction);
			for (String serviceInstanceId: serviceInstanceIds)
			{
				// verify if there is an external service instance dependency
				Collection<ServiceInstanceConfig> dependentInstances =  ManagerFactory.getServiceInstanceManager().getDependentServiceInstances(
						sessionConfig, serviceInstanceId, transaction);
				for (ServiceInstanceConfig dependentInstance: dependentInstances)
				{
					if (!treeChannelIds.contains(dependentInstance.getChannelId()))
					{
						ServiceInstanceConfig serviceInstanceConfig = ManagerFactory.getServiceInstanceManager().get(serviceInstanceId, transaction);
						String channelPath = ManagerFactory.getChannelManager().getPath(sessionConfig, dependentInstance.getChannelId(), transaction);
						throw new ManagerException("STR_SERVICE_INSTANCE_HAS_DEPENDENT_SERVICE_INSTANCE;" +
								TextUtil.escapeLocalizationParameter(serviceInstanceConfig.getName()) + ";" +
								TextUtil.escapeLocalizationParameter(channelPath + "/" + dependentInstance.getName()));

					}
				}

				// delete the service instance
				ManagerFactory.getServiceInstanceManager().delete(sessionConfig, serviceInstanceId, true, transaction);
			}

			// remove local users and groups
			removeLocalUsersAndGroups(sessionConfig, currentChannelId, transaction);

			// delete channel css associations
			List<String> css = getCssIds(sessionConfig, currentChannelId, transaction);
			for (String cssCurrent : css)
				deleteCssLink(sessionConfig, currentChannelId, cssCurrent, transaction);

			// delete channel metatags
			List<String> metaTag = ManagerFactoryInternal.getMetaTagManager().getIdsByParentId(sessionConfig, currentChannelId, transaction);
			for (String metaTagCurrent : metaTag)
				ManagerFactoryInternal.getMetaTagManager().delete(sessionConfig, metaTagCurrent, transaction);

			// delete channel's web resources
			EntityManager entityManager = ManagerFactory.getEntityManager();
			List<?> channelWebResources = entityManager.createQuery("select cwr from ChannelWebResource cwr where cwr.channelId = :channelId")
					.setParameter("channelId", currentChannelId).getResultList();
			for (Object channelWebResource: channelWebResources)
				entityManager.remove(channelWebResource);
			entityManager.flush();

			// delete channel
			channelDao.delete(currentChannelId, transaction);
			channelConfigCache.remove(currentChannelId, transaction);

			// delete channel property bag
			PropertyBag propertyBag = ManagerFactory.getPropertyBagManager().get(currentChannel.getPropertyBagId());
			ManagerFactory.getPropertyBagManager().delete(propertyBag);

			// send delete event
			PostDeleteEvent event = new PostDeleteEvent(sessionConfig, ChannelConfig.class,
					currentChannel, transaction);
			ManagerFactory.getPortalEventManager().notifyObservers(event);

			// delete channel acl
			ManagerFactory.getChannelAclManager().delete(currentChannel.getAccessControlListId(), transaction);
		}
	}

	/**
	 * Reads all channels in a channel tree.
	 * @param sessionConfig user session information.
	 * @param rootChannelId the identifier of the root channel in the tree.
	 * @param channelsStorage the collection where the channel instances
	 * found are added to. The children are added before their parent.
	 * @param transaction the transaction for persistence access.
	 * @since 4.2.0
	 */
	private void getChannelsFromChannelTree(SessionConfig sessionConfig,
			String rootChannelId, Collection<ChannelConfig> channelsStorage,
			ITransaction transaction) throws PortalException
	{
		List<String> subChannelIds = getIdsByParentChannelId(sessionConfig, rootChannelId, transaction);
		for (String channelId: subChannelIds)
			getChannelsFromChannelTree(sessionConfig, channelId, channelsStorage, transaction);
		ChannelConfig channelConfig = get(sessionConfig, rootChannelId, transaction);
		channelsStorage.add(channelConfig);
	}

	public List<String> getIdsByParentChannelId(SessionConfig sessionConfig,
			String channelId, ITransaction transaction) throws PortalException
	{
		String cacheKey = channelId != null ? channelId : NULL_CHANNEL_ID_CACHE_KEY;
		List<String> result = subChannelsCache.get(cacheKey);
		if (result == null)
		{
			result = DaoFactory.getChannelDao().getSubChannelIds(channelId, transaction);
			subChannelsCache.put(cacheKey, result);
		}

		return result;
	}

	public String getPath(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		return getPath(sessionConfig, channelId, false, transaction);
	}

	public String getPath(SessionConfig sessionConfig, String channelId, boolean localizable, ITransaction transaction) throws ManagerException, PortalException
	{
		StringBuilder fullPathName = new StringBuilder();

		for (String channelName : getPathNames(sessionConfig, channelId, localizable, transaction))
		{
			fullPathName.append("/");
			if (localizable)
				fullPathName.append("[lumis:localize]");
			fullPathName.append(channelName);
			if (localizable)
				fullPathName.append("[/lumis:localize]");
		}

		return fullPathName.toString();
	}

	public List<String> getPathNames(SessionConfig sessionConfig, String channelId, boolean localizable, ITransaction transaction) throws ManagerException, PortalException
	{
		ArrayList<String> names = new ArrayList<String>();
		while (channelId != null)
		{
			ChannelConfig channelConfig = get(sessionConfig, channelId, transaction);
			if (channelConfig == null)
				break;
			names.add(0, channelConfig.getName());
			channelId = channelConfig.getParentId();
		}

		return names;
	}

	public String getFriendlyPath(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		if(channelId != null)
		{
			ChannelConfig curChannel = get(sessionConfig, channelId, transaction);
			if (curChannel.getFriendlyPathType() == ChannelConfig.FRIENDLY_PATH_TYPE_CHANNEL_NAME)
			{
				return getFriendlyPath(sessionConfig, curChannel.getParentId(), transaction) + "/" + curChannel.getName();
			}
			else if (curChannel.getFriendlyPathType() == ChannelConfig.FRIENDLY_PATH_TYPE_INHERIT)
			{
				return getFriendlyPath(sessionConfig, curChannel.getParentId(), transaction);
			}
			else if (curChannel.getFriendlyPathType() == ChannelConfig.FRIENDLY_PATH_TYPE_USER_DEFINED)
			{
				String pathUserDefined = curChannel.getFriendlyPathUserDefined();
				if (pathUserDefined != null)
					return pathUserDefined;
				else
					return "";
			}
			else
				throw new ManagerException("STR_UNKNOWN_FRIENDLY_PATH_TYPE");
		}
		else
			return "";
	}

	public List<String> getPathIds(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		List<String> pathIds = new ArrayList<String>();

		String curChannelId = channelId;

		while(curChannelId != null)
		{
			pathIds.add(0, curChannelId);

			ChannelConfig curChannel = get(sessionConfig, curChannelId, transaction);
			if (curChannel == null)
				break;
			curChannelId = curChannel.getParentId();
		}

		return pathIds;
	}

	public void addCssLink(SessionConfig sessionConfig, String channelId, String cssId, int position, ITransaction transaction) throws ManagerException, PortalException
	{
		if (!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, channelId, ChannelPermissions.MANAGE_CHANNEL, transaction))
			throw new AccessDeniedException();

		DaoFactory.getChannelDao().addCss(channelId, cssId, position, transaction);
	}

	public void deleteCssLink(SessionConfig sessionConfig, String channelId, String cssId, ITransaction transaction) throws ManagerException, PortalException
	{
		if (!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, channelId, ChannelPermissions.MANAGE_CHANNEL, transaction))
			throw new AccessDeniedException();

		DaoFactory.getChannelDao().deleteCss(channelId, cssId, transaction);
	}

	public List<String> getCssIds(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		// TODO: validate userSessionsId

		return DaoFactory.getChannelDao().getCssIds(channelId, transaction);
	}

	public List<String> getCssIdsRecursively(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		// TODO: validate userSessionsId

		List<String> cssIds = new ArrayList<String>();

		ChannelConfig channelConfig = get(sessionConfig, channelId, transaction);

		boolean inheritStylesheet = channelConfig.isInheritStylesheet();

		List<String> parentCssIds = null;
		if (inheritStylesheet)
		{
			String parentChannelId = channelConfig.getParentId();

			if (parentChannelId != null)
				parentCssIds = getCssIdsRecursively(sessionConfig, parentChannelId, transaction);
		}


		// First add all non duplicated inherited Css's
		if (parentCssIds != null)
		{
			for (String parentCssId : parentCssIds)
			{
				if (!cssIds.contains(parentCssId))
					cssIds.add(parentCssId);
			}
		}

		// Then add all non duplicated Css's of the channel
		List<String> channelCssIds = DaoFactory.getChannelDao().getCssIds(channelId, transaction);
		for (String channelCssId : channelCssIds)
		{
			if (!cssIds.contains(channelCssId))
				cssIds.add(channelCssId);
		}

		return cssIds;
	}


	public List<String> getMetaTagIdsRecursively(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		List<String> metaTagIds = new ArrayList<String>();
		ChannelConfig channelConfig = get(sessionConfig, channelId, transaction);

		boolean inheritMetaTags = channelConfig.isInheritMetaTags();

		List<String> parentMetaTagIds = null;
		if (inheritMetaTags)
		{
			String parentChannelId = channelConfig.getParentId();

			if (parentChannelId != null)
				parentMetaTagIds = getMetaTagIdsRecursively(sessionConfig, parentChannelId, transaction);
		}


		// First add all inherited MetaTag's
		if (parentMetaTagIds != null)
		{
			for (String parentMetaTagId : parentMetaTagIds)
			{
				metaTagIds.add(parentMetaTagId);
			}
		}

		// Then add all MetaTag's of the channel
		List<String> channelMetaTagIds = ManagerFactoryInternal.getMetaTagManager().getIdsByParentId(sessionConfig, channelId, transaction);
		for (String channelMetaTagId : channelMetaTagIds)
		{
			metaTagIds.add(channelMetaTagId);
		}

		return metaTagIds;
	}

	public boolean getCenterPages(SessionConfig sessionConfig, ChannelConfig channelConfig, ITransaction transaction) throws ManagerException, PortalException
	{
		// TODO: validate userSessionsId

		if (channelConfig.getCenterPages() != PageConfig.PAGE_CENTER_INHERIT)
		{
			return (channelConfig.getCenterPages() == PageConfig.PAGE_CENTER_YES);
		}
		else
		{
			String parentChannelId = channelConfig.getParentId();

			if (parentChannelId == null)
				return false;

			ChannelConfig parentChannelConfig = ManagerFactory.getChannelManager().get(sessionConfig, parentChannelId, transaction);

			return ManagerFactory.getChannelManager().getCenterPages(sessionConfig, parentChannelConfig, transaction);
		}
	}

	public int getLayoutPagesType(SessionConfig sessionConfig, ChannelConfig channelConfig, ITransaction transaction) throws ManagerException, PortalException
	{
		// TODO: validate userSessionsId

		if (channelConfig.getLayoutPagesType() != PageConfig.PAGE_LAYOUT_TYPE_INHERIT)
		{
			return channelConfig.getLayoutPagesType();
		}
		else
		{
			String parentChannelId = channelConfig.getParentId();

			if (parentChannelId == null)
				return PageConfig.PAGE_LAYOUT_TYPE_WITH_TABLES;

			ChannelConfig parentChannelConfig = ManagerFactory.getChannelManager().get(sessionConfig, parentChannelId, transaction);

			return ManagerFactory.getChannelManager().getLayoutPagesType(sessionConfig, parentChannelConfig, transaction);
		}
	}

	public int getCachePages(SessionConfig sessionConfig, ChannelConfig channelConfig, ITransaction transaction) throws ManagerException, PortalException
	{
		// TODO: validate userSessionsId

		if (channelConfig.getCachePages() != PageConfig.PAGE_CACHE_TYPE_INHERIT)
		{
			return channelConfig.getCachePages();
		}
		else
		{
			String parentChannelId = channelConfig.getParentId();

			if (parentChannelId == null)
				return PageConfig.PAGE_CACHE_TYPE_DISABLED;

			ChannelConfig parentChannelConfig = ManagerFactory.getChannelManager().get(sessionConfig, parentChannelId, transaction);

			return ManagerFactory.getChannelManager().getCachePages(sessionConfig, parentChannelConfig, transaction);
		}
	}

	public int getProtocolType(SessionConfig sessionConfig, ChannelConfig channelConfig, ITransaction transaction) throws ManagerException, PortalException
	{
		// TODO: validate userSessionsId

		if (channelConfig.getProtocol() != PageConfig.PAGE_PROTOCOL_INHERIT)
		{
			return channelConfig.getProtocol();
		}
		else
		{
			String parentChannelId = channelConfig.getParentId();

			// if the root channel is inheriting, accept all protocols.
			if (parentChannelId == null)
				return PageConfig.PAGE_PROTOCOL_ALL;

			ChannelConfig parentChannelConfig = ManagerFactory.getChannelManager().get(sessionConfig, parentChannelId, transaction);

			return ManagerFactory.getChannelManager().getProtocolType(sessionConfig, parentChannelConfig, transaction);
		}
	}

	public void serialize(SessionConfig sessionConfig, String channelId, OutputStream outputStream, ITransaction transaction) throws ManagerException, PortalException
	{
		// TODO: validate userSessionId
		ChannelConfig channelConfig = get(sessionConfig, channelId, transaction);
		channelConfig.serialize(channelConfig, outputStream);
	}

	public String deserialize(SessionConfig sessionConfig, DeserializationConfig deserializationConfig, Node channelNode, ITransaction transaction) throws ManagerException, PortalException
	{
		ChannelConfig channelConfig = new ChannelConfig();
		channelConfig.deserialize(channelNode);

		if(deserializationConfig.getCreateNewIds())
		{
			String newId = PortalUtil.generateNewGuid();
			deserializationConfig.setIdMapping(channelConfig.getId(), newId);
			channelConfig.setId(newId);

			if(DaoFactory.getChannelDao().checkForChannelPrefix(channelConfig, transaction))
			{
				Pattern p = Pattern.compile(" ");
				Matcher m = p.matcher(channelConfig.getName().toLowerCase());

				String modifiedChannelName = m.replaceAll("");

				int iPrefixIndex = 0;
				String localGroupsPrefix = modifiedChannelName;
				channelConfig.setLocalGroupPrefix(localGroupsPrefix);
				while(DaoFactory.getChannelDao().checkForChannelPrefix(channelConfig, transaction))
				{
					iPrefixIndex++;
					localGroupsPrefix = modifiedChannelName + iPrefixIndex;
					channelConfig.setLocalGroupPrefix(localGroupsPrefix);
				}
			}
		}

		// now fix ids based on mappings and
		if(channelConfig.getParentId() != null && deserializationConfig.containsIdMapping(channelConfig.getParentId()))
			channelConfig.setParentId(deserializationConfig.getIdMapping(channelConfig.getParentId()));

		if(channelConfig.getAccessControlListId() != null && deserializationConfig.containsIdMapping(channelConfig.getAccessControlListId()))
			channelConfig.setAccessControlListId(deserializationConfig.getIdMapping(channelConfig.getAccessControlListId()));

		// do not deserialize link with parent template
		// Channel template information must be deserialized separately case because
		// of possible foreign key problems
		channelConfig.setParentTemplateId(null);

		// do not deserialize pageTemplateId, it will be set at deserializeSecondPass
		channelConfig.setPageTemplateId(null);

		if(deserializationConfig.isUpdateIfElementExists())
			addOrUpdate(sessionConfig, channelConfig, transaction);
		else
			add(sessionConfig, channelConfig, transaction);

		return channelConfig.getId();
	}

	public void deserializeSecondPass(SessionConfig sessionConfig, DeserializationConfig deserializationConfig, Node channelNode, ITransaction transaction) throws ManagerException, PortalException
	{
		// read data from channelNode
		ChannelConfig channelConfig = new ChannelConfig();
		channelConfig.deserialize(channelNode);

		// read channel to be updated
		String channelId = deserializationConfig.getIdMapping(channelConfig.getId());
		ChannelConfig channelToBeUpdated = get(sessionConfig, channelId, transaction);

		// if there is a pageTemplateId specified, it must be updated in the
		// imported channel
		String pageTemplateId = deserializationConfig.getIdMapping(channelConfig.getPageTemplateId());
		if (pageTemplateId != null)
		{
			channelToBeUpdated.setPageTemplateId(pageTemplateId);
			update(sessionConfig, channelToBeUpdated, transaction);
		}

		// deserialize channel's property bags
		Node[] propertyNodes = XmlUtil.selectNodes("propertyBag/p", channelNode);
		PropertyBag propertyBag = ManagerFactory.getPropertyBagManager().get(channelToBeUpdated.getPropertyBagId());
		Set<String> propertiesNamesModified = new HashSet<String>();
		if(propertyNodes.length > 0)
		{
			for(Node propertyNode: propertyNodes)
			{
				String name = XmlUtil.readNodeString("n", propertyNode);
				boolean isPrivate = XmlUtil.readNodeBoolean("pvt", propertyNode, false);

				Node[] valueNodes = XmlUtil.selectNodes("vs/v", propertyNode);
				ArrayList<String> values = new ArrayList<String>(valueNodes.length);
				for(Node valueNode : valueNodes)
					values.add(valueNode.getTextContent());

				propertyBag.setDefaultValues(name, values);
				propertyBag.getLocalProperty(name).setPrivate(isPrivate);
				propertiesNamesModified.add(name);
			}
		}

		// list properties not in template
		Map<String, Property> propertyBagProperties = propertyBag.getLocalPropertyMap();
		HashSet<String> propertiesToRemove = new HashSet<String>(propertyBagProperties.keySet().size());
		for(String key : propertyBagProperties.keySet())
		{
			if(!propertiesNamesModified.contains(key))
				propertiesToRemove.add(key);
		}
		// remove properties not in template
		for(String propertyName : propertiesToRemove)
		{
			propertyBag.removeProperty(propertyName);
		}
	}

	public void serializeCss(SessionConfig sessionConfig, String channelId, OutputStream outputStream, ITransaction transaction) throws ManagerException, PortalException
	{
		try
		{
			// serialize css links
			List<String> cssIds = getCssIds(sessionConfig, channelId, transaction);
			StringBuilder stream = new StringBuilder();
			stream.append("<cssLinks channelId=\"" + channelId + "\">");
			for(String cssId : cssIds)
				stream.append("<cssLink cssId=\"" + cssId + "\"/>");
			stream.append("</cssLinks>");
			outputStream.write(stream.toString().getBytes("UTF-8"));
		}
		catch(Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	public void deserializeCss(SessionConfig sessionConfig, DeserializationConfig deserializationConfig, Node cssLinksNodes, ITransaction transaction) throws ManagerException, PortalException
	{
		Node[] cssLinksNode = XmlUtil.selectNodes("cssLink", cssLinksNodes);
		String channelId = XmlUtil.readAttributeOrNodeString(cssLinksNodes, "channelId");

		if(deserializationConfig.getCreateNewIds())
			channelId = deserializationConfig.getIdMapping(channelId);

		for(int i = 0; i < cssLinksNode.length; i++)
			addCssLink(sessionConfig, channelId, XmlUtil.readAttributeOrNodeString(cssLinksNode[i], "cssId"), i, transaction);
	}

	public List<String> getIdsByParentTemplateId(SessionConfig sessionConfig, String parentTemplateId, ITransaction transaction) throws ManagerException, PortalException
	{
		return DaoFactory.getChannelDao().getIdsByParentTemplateId(parentTemplateId, transaction);
	}

	/**
	 * Clears cache for specified channels recursively.
	 * ACLs and pages are not cleared.
	 * @param sessionConfig the user session information.
	 * @param channelIds the channel identifiers.
	 * @param pageIds the identifiers of the pages inside the channels.
	 * @param transaction the transaction for persistence access.
	 * @since 4.1.0
	 */
	private void clearChannelCacheRecursively(SessionConfig sessionConfig,
			Collection<String> channelIds, Collection<String> pageIds, ITransaction transaction) throws PortalException
	{
		ServiceInstanceManager serviceInstanceManager = (ServiceInstanceManager)ManagerFactory.getServiceInstanceManager();
		for (String channelId: channelIds)
		{
			// clear ChannelConfig cache. subChannelsCache is cleared in the clearCache method
			channelConfigCache.remove(channelId, transaction);

			// clear service instances cache
			Collection<String> serviceInstanceIds = serviceInstanceManager.getIdsByChannelId(sessionConfig, channelId, transaction);
			for(String serviceInstanceId : serviceInstanceIds)
				serviceInstanceManager.clearServiceInstanceMemoryCache(serviceInstanceId, transaction);

			// clear cache of sub-channels
			List<String> subChannelIds = getIdsByParentChannelId(sessionConfig, channelId, transaction);
			clearChannelCacheRecursively(sessionConfig, subChannelIds, pageIds, transaction);

			// add pages of this channel in pageIds
			pageIds.addAll(ManagerFactory.getPageManager().getIdsByParentChannelId(
					sessionConfig, channelId, transaction));
		}
	}

	public void clearCache(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws ManagerException, PortalException
	{
		if(channelId.equals(ChannelConfig.PORTAL_CHANNEL_ID))
		{
			if (!ManagerFactory.getPortalAclManager().checkPermission(sessionConfig, PortalPermissions.MANAGE_CACHE, transaction))
				throw new AccessDeniedException();

			// clear all memory cache
			PortalCache.clearAll();

			// clear page HTML cache
			ManagerFactoryInternal.getPageCacheManager().clearAllHTMLCache(transaction);

			// clear all memory cache after commit
			transaction.addObserver(new AbstractTransactionObserver()
			{
				@Override
				public void afterCommit() throws PortalException
				{
					PortalCache.clearAll();
				}
			});
		}
		else
		{
			// clear channels cache
			Collection<String> pageIds = new ArrayList<String>(1000);
			clearChannelCacheRecursively(sessionConfig, Collections.singleton(channelId), pageIds, transaction);
			subChannelsCache.clear(transaction);

			// clear pages cache
			ManagerFactory.getPageManager().clearCache(sessionConfig, pageIds, transaction);

			// clear all ACL cache
			ManagerFactory.getChannelAclManager().clearCache(transaction);
		}
	}

	public String getPageTemplateIdRecursively(SessionConfig sessionConfig, String channelId, ITransaction transaction) throws PortalException
	{
		ChannelConfig parentChannelConfig = get(sessionConfig, channelId, transaction);
		if (parentChannelConfig.isInheritPageTemplate())
		{
			if (parentChannelConfig.getParentId() == null)
				return null;
			else
				return getPageTemplateIdRecursively(sessionConfig, parentChannelConfig.getParentId(), transaction);
		}
		else
		{
			return parentChannelConfig.getPageTemplateId();
		}
	}

	public int getChannelTreeCount(String rootChannelId, ITransaction transaction) throws ManagerException, PortalException
	{
		return DaoFactory.getChannelDao().getChannelTreeCount(rootChannelId, transaction);
	}

	public List<String> getChannelTreeIds(String rootChannelId, ITransaction transaction) throws ManagerException, PortalException
	{
		return DaoFactory.getChannelDao().getChannelTreeIds(rootChannelId, transaction);
	}

	public Collection<String> getIdsByPageTemplateId(SessionConfig sessionConfig, String pageTemplateId, ITransaction transaction) throws ManagerException, PortalException
	{
		return DaoFactory.getChannelDao().getIdsByPageTemplateId(pageTemplateId, transaction);
	}

	public boolean keyExists(SessionConfig sessionConfig, Object configKey, Object config, ITransaction transaction)
	{
		ChannelConfig parentChannelTemplateConfig = null;
		try
		{
			parentChannelTemplateConfig = get(sessionConfig, (String)configKey, transaction);
		}
		catch (PortalObjectNotFoundException e)
		{
		}
		catch (PortalException e)
		{
			throw new IllegalStateException("Could not check a channel key persistence.");
		}
		return (parentChannelTemplateConfig != null);
	}

	@Deprecated
	public String getRuntimeDataProviderClassName(SessionConfig sessionConfig, ChannelConfig channelConfig, ITransaction transaction) throws PortalException
	{
		return getPageWebResourceDataProviderClassName(sessionConfig, channelConfig, transaction);
	}

	public String getPageWebResourceDataProviderClassName(
			SessionConfig sessionConfig, ChannelConfig channelConfig,
			ITransaction transaction) throws PortalException
	{
		try
		{
			String providerClassName = DefaultPageWebResourceDataProvider.class.getName();

			if(channelConfig.getPageWebResourceDataProviderType() != PageConfig.PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_INHERIT)
			{
				if(channelConfig.getPageWebResourceDataProviderType() == PageConfig.PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_DEFAULT)
					providerClassName = DefaultPageWebResourceDataProvider.class.getName();
				else if(channelConfig.getPageWebResourceDataProviderType() == PageConfig.PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CONTENT)
					providerClassName = "lumis.content.webresource.ContentPageWebResourceDataProvider";
				else if(channelConfig.getPageWebResourceDataProviderType() == PageConfig.PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CUSTOM)
					providerClassName = channelConfig.getPageWebResourceDataProviderClassName();
			}
			else
			{
				String parentChannelId = channelConfig.getParentId();

				if(parentChannelId != null)
				{
					ChannelConfig parentChannelConfig = ManagerFactory.getChannelManager().get(sessionConfig, parentChannelId, transaction);
					return getPageWebResourceDataProviderClassName(sessionConfig, parentChannelConfig, transaction);
				}
			}

			return providerClassName;
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	// -- IWebResourceFactory -- //

	/**
	 * Regexp pattern used in {@link #createWebResourceByDynamicPath(String)}
	 * for obtaining channel identifier from dynamic path.
	 * @since 5.0.0
	 */
	private static Pattern DYNAMIC_PATH_CHANNEL_ID = Pattern.compile("\\W" + PortalRequestParameters.PAGE_PARAMETER_CHANNELID + "=([^&]+)");

	/**
	 * @since 5.0.0
	 */
	public WebResource createWebResourceByDynamicPath(String dynamicPath) throws PortalException
	{
		// channel web resource's dynamic path must contain only the channelId parameter
		if (dynamicPath.indexOf('&') != -1)
			throw new IllegalArgumentException("Channel web resource must not contain extra parameters");

		// read channelId from dynamicPath
		Matcher channelIdMatcher = DYNAMIC_PATH_CHANNEL_ID.matcher(dynamicPath);
		if (!channelIdMatcher.find())
			throw new IllegalArgumentException("Could not obtain channelId from dynamic path: " + dynamicPath);
		String channelId = channelIdMatcher.group(1);

		// check that channel exists
		try
		{
			get(null, channelId, PortalTransactionFactory.getCurrentTransaction());
		}
		catch (PortalObjectNotFoundException e)
		{
			throw new IllegalArgumentException("Could not find channel for dynamic path '" + dynamicPath + "'; Channel id='" + channelId + "' not found", e);
		}

		// create ChannelWebResource
		ChannelWebResource webResource = new ChannelWebResource();
		webResource.setChannelId(channelId);
		return webResource;
	}

	/**
	 * @since 5.0.0
	 */
	public void setPageCacheFilePath(PageCacheConfig pageCacheConfig) throws PortalException
	{
		// calculate file extension
		ChannelWebResource channelWebResource = (ChannelWebResource)pageCacheConfig.getWebResource();
		String extension = channelWebResource.getRecommendedFileExtension();

		// calculate path for cache file
		String filePath = null;
		if (channelWebResource.isFriendlyURLEnabled())
			filePath = channelWebResource.getFriendlyPath();
		if (filePath == null)
			filePath = "/data/pages/" + pageCacheConfig.getId() + extension;
		else
			filePath += "/index" + extension;

		// set the filePath
		pageCacheConfig.setFilePath(filePath);
	}

	public boolean isCacheEnabled(WebResource webResource) throws PortalException
	{
		String channelId = ((ChannelWebResource)webResource).getChannelId();
		return isCacheEnabled(channelId);
	}

	/**
	 * Returns if the cache is enabled for the first page of a channel.
	 * @param channelId the channel identifier.
	 * @return <code>true</code> if the cache is enabled, <code>false</code> otherwise.
	 * @since 5.0.0
	 */
	private boolean isCacheEnabled(String channelId) throws PortalException
	{
		try
		{
			// get channel's default page
			PageConfig page = ManagerFactory.getPageManager().getFirstPageByChannelIdRecursively(
					SessionConfig.getCurrentSessionConfig(), channelId,
					PortalTransactionFactory.getCurrentTransaction());

			// check page cache type
			int pageCacheType = ManagerFactory.getPageManager().getCachePage(SessionConfig.getCurrentSessionConfig(),
					page, PortalTransactionFactory.getCurrentTransaction());
			return pageCacheType != PageConfig.PAGE_CACHE_TYPE_DISABLED;
		}
		catch (PortalObjectNotFoundException e)
		{
			// if the channel has no page, assume its cache is disabled
			return false;
		}
	}

	public void setWebResourcesMissingData(String resourceId, boolean processChildren) throws PortalException
	{
		// if this channel was already processed in the current transaction,
		// no need to process it again
		ITransaction transaction = PortalTransactionFactory.getCurrentTransaction();
		if (transaction.getAttribute(ATTRIBUTE_PREFIX_CHANNEL_WEB_RESOURCE_SET + resourceId) != null)
			return;

		SessionConfig sessionConfig = SessionConfig.getCurrentSessionConfig();
		ChannelConfig channelConfig;
		try
		{
			channelConfig = get(sessionConfig, resourceId, transaction);
		}
		catch (PortalObjectNotFoundException e)
		{
			// if the channel does not exist anymore, no need to adjust it
			return;
		}
		doSetWebResourcesMissingData(channelConfig, processChildren);
	}

	/**
	 * Sets missing data in web resources of a channel and its sub-channels
	 * and pages.
	 * @param channelConfig the channel.
	 * @param processChildren indicates if web resources of children resources
	 * should also be processed.
	 * @since 5.0.0
	 */
	@SuppressWarnings("unchecked")
	private void doSetWebResourcesMissingData(ChannelConfig channelConfig, boolean processChildren) throws PortalException
	{
		// read configuration information
		SessionConfig sessionConfig = SessionConfig.getCurrentSessionConfig();
		IPageManager pageManager = ManagerFactory.getPageManager();
		ITransaction transaction = PortalTransactionFactory.getCurrentTransaction();
		int cachePages;
		try
		{
			// try to get the default page for this channel and use its cache configuration
			PageConfig pageConfig = pageManager.getFirstPageByChannelIdRecursively(sessionConfig,
					channelConfig.getId(), transaction);
			cachePages = pageManager.getCachePage(sessionConfig, pageConfig, transaction);
		}
		catch(PortalObjectNotFoundException e)
		{
			//if there's no default page get channel's cachePage
			cachePages = getCachePages(sessionConfig, channelConfig, transaction);
		}

		boolean cacheEnabled = cachePages != PageConfig.PAGE_CACHE_TYPE_DISABLED;
		PropertyBag propertyBag = ManagerFactory.getPropertyBagManager().get(channelConfig.getPropertyBagId());
		boolean friendlyURLEnabled = Boolean.parseBoolean(propertyBag.getValue(IURLManager.FRIENDLY_URL_ENABLE_PROPERTY_NAME, "false"));

		if (cacheEnabled || friendlyURLEnabled)
		{
			// obtain web resources to be adjusted
			StringBuilder ql = new StringBuilder(120);
			ql.append("select cwr from ChannelWebResource cwr ");
			if (cacheEnabled)
				ql.append("left outer join cwr.pageCache pc ");
			ql.append("where cwr.channelId = :channelId and (");
			if (cacheEnabled)
			{
				ql.append("(pc is null or ");
				if (friendlyURLEnabled)
					ql.append("pc.filePath LIKE '/data/pages/%')");
				else
					ql.append("pc.filePath NOT LIKE '/data/pages/%')");

				if (friendlyURLEnabled)
					ql.append(" or ");
			}
			if (friendlyURLEnabled)
				ql.append(" cwr.friendlyPath is null");
			ql.append(")");
			Collection<WebResource> webResources = ManagerFactory.getEntityManager().createQuery(ql.toString())
					.setParameter("channelId", channelConfig.getId())
					.getResultList();

			// adjust web resources
			ManagerFactoryInternal.getURLManager().setWebResourcesMissingData(
					this, webResources, cacheEnabled, friendlyURLEnabled);
		}

		// mark that the web resource for this channel was adjusted in this transaction
		PortalTransactionFactory.getCurrentTransaction().setAttribute(
				ATTRIBUTE_PREFIX_CHANNEL_WEB_RESOURCE_SET + channelConfig.getId(), Boolean.TRUE);

		if (processChildren)
		{
			// adjust the web resource of the sub-channels
			Collection<String> subChannelIds = getIdsByParentChannelId(
					sessionConfig, channelConfig.getId(), transaction);
			for (String subChannelId: subChannelIds)
				setWebResourcesMissingData(subChannelId, true);

			// adjust the web resource of the sub-pages
			Collection<String> pageIds = pageManager.getIdsByParentChannelId(
					sessionConfig, channelConfig.getId(), transaction);
			for (String pageId: pageIds)
				((IWebResourceFactory)pageManager).setWebResourcesMissingData(pageId, true);
		}

		if (cacheEnabled)
		{
			// adjust the web resource of parent channel (due to cache in default page)
			String parentId = channelConfig.getParentId();
			if (parentId != null)
				setWebResourcesMissingData(parentId, false);
		}
	}

	public String adjustResolvedFriendlyPath(WebResource webResource, String friendlyPath)
	{
		return friendlyPath;
	}

	public String adjustFriendlyPathForClash(String originalFriendlyPath, String clashedFriendlyPath, int numRetry)
	{
		return originalFriendlyPath + "-" + numRetry;
	}

	/**
	 * Indicates if the given channel must have its friendly identifier
	 * regenerated. This method must be called <em>before</em> the update is
	 * realized.
	 * 
	 * @param sessionConfig
	 *            the session config.
	 * @param channelConfig
	 *            the channel to be tested.
	 * @param transaction
	 *            the transaction.
	 * @return true if the given channel must have its friendly identifier
	 *         regenerated, false otherwise.
	 * @since 6.0.0
	 */
	private boolean mustUpdateFriendlyId(SessionConfig sessionConfig,
			ChannelConfig channelConfig, ITransaction transaction) throws PortalException
	{
		if (channelConfig.isAutomaticFriendlyId() && channelConfig.getFriendlyId() == null)
			return true;

		ChannelConfig current = get(sessionConfig, channelConfig.getId(), transaction);
		if (!channelConfig.isAutomaticFriendlyId() || (channelConfig.getParentId() == null && channelConfig.getParentId() == null))
			return false;

		boolean becomeAutomatic = !(current.isAutomaticFriendlyId())
				&& channelConfig.isAutomaticFriendlyId();
		boolean changedName = !(current.getName().equals(channelConfig.getName()));
		boolean changedParent = !(current.getParentId().equals(channelConfig.getParentId()));
		return (changedName || changedParent || becomeAutomatic);
	}

	/**
	 * Indicates if the given channel may use the given friendly identifier.
	 * 
	 * @param source
	 *            the channel.
	 * @param friendlyId
	 *            the friendly identifier.
	 * @return true if the given channel may use the given friendly identifier,
	 *         false otherwise.
	 * @since 6.0.0
	 */
	boolean mayUseFriendlyId(ChannelConfig source, String friendlyId) throws PortalException
	{
		String channelId = null;
		if (source.getId() != null)
		{
			try
			{
				get(SessionConfig.getCurrentSessionConfig(), source.getId(),
						PortalTransactionFactory.getCurrentTransaction());
				channelId = source.getId();
			}
			catch (PortalObjectNotFoundException e)
			{
				// ignore it
			}
		}

		int numberOfChannelsUsingSameFriendlyId = ((ChannelDaoJdbc) DaoFactory.getChannelDao())
				.getNumberOfChannelsUsingSameFriendlyId(channelId, source.getParentId(),
						friendlyId, PortalTransactionFactory.getCurrentTransaction());
		return numberOfChannelsUsingSameFriendlyId == 0;
	}
}
